﻿import socket
import time
import threading
import json

import timeout
from platform.utilits import *

#пользователи
super_users = [USR_GAMEMASTER]

#класс для "внутреннего использования", обеспечивает поток для каждого очередного вновь установленного соединения
class recvThread(threading.Thread):
	def __init__(this, conn):
		threading.Thread.__init__(this)
		this.conn = conn
		this.setDaemon(True)
		liba.connections.append([None, conn])
	
	#крутит бесконечный цикл прослушки соединения сокета, в случае обрыва или прихода пустого сообщения - 
	#прерывает свой бег, уничтожает соединение, генерирует событие BYE от имени пользователя
	def run(this):
		this.reg = False
		while 1:
			try:
				inf = this.conn.recv(liba.tcp_port)
				if len(inf) == 0:
					this._unregisterconn()
					this.conn.close()
					break
				msg = this._parce(inf)
				#принятое сообщение будет засунуто в обработку только в случае, если клиент идентифицирован
				#или либа запущена в режиме клиента (тогда единственное соединение - с сервером)
				if not this.reg and liba.isServer():
					this.reg = this._registerconn(msg)
				if this.reg or liba.isClient():
					liba.mail(msg)
			except IOError, errormsg:
				this._unregisterconn()
				this.conn.close()
				break
	
	#в ряде случаев надо вычеркнуть соединение из таблицы и послать от имени юзера сообщение BYE в обработку
	def _unregisterconn(this):
		if liba.isServer():
			for conn in liba.connections:
				if conn[1] == this.conn:
					usr = conn[0]
					liba.connections.remove(conn)				
					liba.mail({MSG_USR: usr, MSG_TYPE: "bye"})
					return
		else:
			liba.mail({MSG_USR: USR_SERVER, MSG_TYPE: "bye"})

		
	#если клиент сказал HI и назвал свой логин - впишем его соединение с логином в таблицу подключений и впредь будем обрабатывать
	#его сообщения на общих основаниях
	def _registerconn(this, msg):
		if  msg.has_key(MSG_USR) and msg.get(MSG_TYPE) == "hi":
			for conn in liba.connections:
				if conn[1] == this.conn:
					conn[0] = msg[MSG_USR]
					break
			return True
		return False
	
	#эмуляция парсинга - каждый "пакет" должен состоять из 3х полей с разделителем ";"
	#эти поля: Юзер, Тип сообщения, Тело сообщения, например: "USER1;TURN;1:2"
	#пользователь USER1 пытается сделать ход в поле с координатами (1, 2)
	def _parce(this, inf):
		data = str.split(inf, ";")
		if data[2] == "":
			data[2] = "null"
		return {MSG_USR: data[0], MSG_TYPE: data[1], MSG_BODY: data[2]}

#класс для внутреннего использования - в случае серверного варианта работы либы
#создает сокет, а для каждого нового соединения стартует свой тред соединения
class serverThread(threading.Thread):
	def __init__(this):
		threading.Thread.__init__(this)
		this.do = True
		this.setDaemon(True)
	def run(this):
		s = socket.socket()
		s.bind(('localhost', liba.tcp_port))
		s.listen(5)
		while this.do == True:
			conn, addr = s.accept()
			recv = recvThread(conn)
			recv.start()
	def stop(this):
		this.do = False

#класс для внутреннего использования - в случае клиентского варианта работы либы
#создает сокет и коннектиться к серверу, стартует единственный тред прослушки соединения
def clientThread():
	s = socket.socket()
	s.connect(('localhost', liba.tcp_port))
	recv = recvThread(s)
	recv.start()

class Lib:
	"""
	реализует эмуляцию функционала либы bgtp для прототипирования, обеспечивает интерфейс, описанный здесь: http://code.google.com/p/bgtp/wiki/ProtocolSpec
	описание смотреть там
	"""
	
	def __init__(this):
		this.subscribes = []
		this.values= {}
		this.connections = [] #лист листов; каждый - логин и соединение
		this.mode = None #тип запуска: "Client" или "Server"
		this.serverThread = None
		this.liveTimeout = None
		this.tcp_port = TCP_PORT
	
	def _init_from_file_(this):
		pass
	def _init_default_(this):
		pass
	def _filter(this, header, filter):
		for key in filter.keys():
			if not header.has_key(key) or filter[key].count(header[key]) == 0:
				return False
		return True
	def _msg_to_str(this, msg):
		try:
			res = "> " + msg.get(MSG_USR, "???") + "::" + msg.get(MSG_TYPE, "???") + "(" + str(json.loads(msg.get(MSG_BODY, 'null'))) + ")"
			return res
		except:
			return "bgtp::error msg!!!", msg
	def _mailto(this, stream, msg):
		if type(stream) == file:
			stream.writelines([this._msg_to_str(msg), '\n'])
		elif type(stream) == list:
			stream.append(msg)
		elif type(stream) == str and stream == 'stdout':
			print this._msg_to_str(msg)
	def _stopstreams(this):
		for subscribe in this.subscribes:
			filter, stream = subscribe
			if type(stream) == file:
				stream.close()
	def _sendTo(this, conn, msg):
		try:
			conn.send(msg)
		except:
			print "bgtp can,t send"
	def isClient(this):
		return this.mode == "Client"
	def isServer(this):
		return this.mode == "Server"
	def _setLiveTimeout(this, livetimeout, timeout_tic = 0.01):
		if this.liveTimeout != None:
			this.liveTimeout.stop()
		this.liveTimeout = timeout.TimeOut(timeout_tic, livetimeout, {}, {MSG_TYPE: "timeout", MSG_USR: USR_SERVER})
		this.liveTimeout.start()
	
	#########################################
	#интерфейс
	def init(this, params = None):
		if type(params) == str:
			this._init_from_file_(open(params))
		elif type(params) == dict:
			this.values.update(params)
		elif params == None:
			this._init_default_()
		else:
			raise Exception("bgtp.Lib.init() - Unknown type of params: " + params)
	
	def startServer(this):
		this.mode = "Server"
		this.serverThread = serverThread()
		this.serverThread.start()
		print "start server"
	
	def startClient(this):
		this.mode = "Client"
		clientThread()
		print "start client"
	
	def subscribe(this, filter, stream):
		"""
		возможные варианты применения подписки:
			подписка на лист для последующего разбора в бесконечном цикле
			с применением фильтра на некоторые виды событий
			stream = []
			liba.subscribe({MSG_TYPE: ["STATE", "YOUTURN", "BYE", "ENDGAME"]}, stream)
			
			логирование всех событий в файл
			out = open("ai_log.txt", 'w')
			liba.subscribe({}, out)			
			
			вывод всех принимаемых сообщений в консоль (например для отладки)
			liba.subscribe({}, 'stdout')			
		"""
		this.subscribes.append((filter, stream))
	
	def getConnections(this):
		return this.connections
	
	def setValue(this, valtype, value):
		this.values[valtype] = value
		
		if valtype == "timeout":
			this._setLiveTimeout(value)
	
	def getValue(this, valtype):
		if this.values.has_key(valtype):
			return this.values[valtype]
		return None #может лучше тут исключение? todo
	
	def send(this, msg):
		for conn in this.connections:
			this._sendTo(conn[1], msg)
	
	def sendTo(this, usr, msg):
		if this.isClient():
			raise Exception("cann't send to... for client mode")
		if usr == "":
			this.send(msg)
		conns = [conn[1] for conn in this.connections if conn[0] == usr]
		for conn in conns:
			this._sendTo(conn, msg)
	
	def mail(this, msg):
		for subscribe in this.subscribes:
			filter, stream = subscribe
			if this._filter(msg, filter):
				this._mailto(stream, msg)
	
	def stop(this):
		if this.liveTimeout != None:
			this.liveTimeout.stop()
		if this.isServer() and this.serverThread != None:
			this.serverThread.stop()
		this._stopstreams()

liba = Lib()